{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import pandas as pd\n",
    "import numpy as np"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Melbourne Housing Market"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Датасет представляет из себя цены на квартире в мельбурне. Данные взять с kaggle (https://www.kaggle.com/anthonypino/melbourne-housing-market/data).\n",
    "Я поставил такую цель - научиться предсказывать цену по параметрам недвижимости.\n",
    "Это может быть актуально при создании объявлений о покупке\\аредны жилья. Вряд ли кто-то захочет сильно переплачивать или недополучать за недвижимость, если в 100 метрах рядом есть такое же.\n",
    "В качестве целевого признака будет использовать Price"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df_raw = pd.read_csv('../data/Melbourne_housing_FULL.csv')\n",
    "df_raw[\"Date\"] = pd.to_datetime(df_raw[\"Date\"])\n",
    "df_raw.head()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df_raw.info()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Как видим, по целевому признаку есть пропуски. Уже есть цель их восстановить:) \n",
    "8 - категориальных \n",
    "13 - num признаков"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Примерный перевод признаков\n",
    "\n",
    "**Suburb**: Название пригорода\n",
    "\n",
    "**Address**: Адресс\n",
    "\n",
    "**Rooms**: Количество комнат\n",
    "\n",
    "**Price**: Цена в долларах\n",
    "\n",
    "**Method**: S - property sold; SP - property sold prior; PI - property passed in; PN - sold prior not disclosed; SN - sold not disclosed; NB - no bid; VB - vendor bid; W - withdrawn prior to auction; SA - sold after auction; SS - sold after auction price not disclosed. N/A - price or highest bid not available.\n",
    "\n",
    "**Type**: br - bedroom(s); h - дом, коттедж, вила; u - блок домов, дуплекс; t - таун хаус; dev site - в процессе постройки; o res - other residential.\n",
    "\n",
    "**SellerG**: Аген по недвижимости\n",
    "\n",
    "**Date**: Дата продажи\n",
    "\n",
    "**Distance**: Расстояние до CBD (Central Business District)\n",
    "\n",
    "**Regionname**: Регион (West, North West, North, North east ...etc)\n",
    "\n",
    "**Propertycount**: количество продающихся домов в данном пригороде.\n",
    "\n",
    "**Bedroom2**: Scraped # of Bedrooms (from different source)\n",
    "\n",
    "**Bathroom**: Количество ванных комнат\n",
    "\n",
    "**Car**: Количество машиноместа\n",
    "\n",
    "**Landsize**: Площадь участка\n",
    "\n",
    "**BuildingArea**: Размер дома\n",
    "\n",
    "**YearBuilt**: Год постройки\n",
    "\n",
    "**CouncilArea**: Какой гос совет тут руководит\n",
    "\n",
    "**Lattitude, Longtitude** - координаты\t"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "## Уберем записи с пропущенным price\n",
    "print(df_raw.shape)\n",
    "\n",
    "df_missed = df_raw[df_raw['Price'].isnull()]\n",
    "df = df_raw[df_raw['Price'].notnull()]\n",
    "\n",
    "print(df_missed.shape, df.shape)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df_p = df.drop('Price', axis=1)\n",
    "y = df.Price.values"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Работа с исходными данными и визуацлизаця"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import seaborn as sns\n",
    "from scipy.stats import normaltest, shapiro, skewtest\n",
    "import matplotlib.pyplot as plt"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Целевой признак"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "sns.distplot(y);"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Как видим, у переменной большой хвост справа. Это вполне ожидаемо: любой каприз за ваши деньги. Такую велечину плохо прогнозировать. Попробуем её логорифмировать:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "sns.distplot(np.log(y));"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Уже больше похоже на нормальное распределение.\n",
    "\n",
    "Протестируем на нормальность и скошенность:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "k2, p = normaltest(np.log(y))\n",
    "print('normaly:', k2, p)\n",
    "\n",
    "k2, p = skewtest(np.log(y))\n",
    "print('skewness:', k2, p)\n",
    "\n",
    "## Тест на нормальность и скошенность не пройден"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Построим нормальное с таким же средним и стандартным отклонением"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "a = np.random.normal(np.mean(np.log(y)), np.std(np.log(y)), size=len(y))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "sns.distplot(np.log(y));\n",
    "sns.distplot(a);"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Распределения очень похожи. По всей видимости, целевая переменная не проходит тест из-за бимодальности. Резкий всплекс цен приходится на exp(14) ~ 1.2млн$. Возможно это как-то связано с местным законодатесльвом.\n",
    "\n",
    "\n",
    "**Будем считать, что логорифмированные данные нормальны и не скошенны.**"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Взаимодействие признаков"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Корреляции"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "a4_dims = (11.7, 8.27)\n",
    "fig, ax = plt.subplots(figsize=a4_dims)\n",
    "\n",
    "sns.heatmap(df.corr(), annot=True, ax=ax);"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Выводы**:\n",
    "\n",
    "Как видно, сильно скоррелированы Rooms и Bedroom2. Фактически это один и тот же признак, но второй с пропусками.\n",
    "\n",
    "Так же с количеством комнат скоррелированы - количество ванных комнат, машиномест и цена. Это тоже интуитивно понятно - чем больше жильё, тем больше в нем комнат, ванных, машиномест и тем оно дороже.\n",
    "\n",
    "Год постройки и дистанция \"немного\" скоррелированы. Это можно объяснить тем, что в центре уже нет мест для новых домов. И все новое строится далеко.\n",
    "\n",
    "Чем старше дом - тем он дешевле."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Попарное распределение"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "sns.pairplot(df.dropna());"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Если скачать картинку, то лучше будет видно.\n",
    "\n",
    "Треугольники в правых и верхних углах - это дистанция от центра в зависиомти от координаты.\n",
    "\n",
    "Рассмотрим второй столбец (цена). Можно заметить а) есть максимумы цены по коориднатам (похоже на центр) 2) Цена не так сильно зависит от размера жилья, как это могло показаться\n",
    "\n",
    "Есть выбросы по площади постройки, году постройки. Имеет смысл убрать дома до 1200 и площадью более 1500."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Зависимость цены от типа жилья и времени"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "price = df.sort_values(\"Date\", ascending=False).groupby([\"Date\", 'Type'])[['Price']].mean().reset_index()\n",
    "price.head()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "sns.pointplot(x='Date', y='Price', data=price, hue='Type');"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Явного тренда нет"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Пропуски\n",
    "Работает с данными, где нет пропусков по цене"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df_p.info()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df_p[df_p.isnull().any(axis=1)]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Как видим, пропуски идут \"пачками\". \"Bedroom2\" из-за скорелированости с Room (было показано ранее). Коориднаты можно брать как средние по району\\улице. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df_p.drop('Bedroom2', axis=1, inplace=True)\n",
    "df_p.head()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Ввиду громоздкости это не вошло в ноутбук, так что пусть будет как факт: четкой **связи между** риэлетром\\типом постройки и пропусками **нет**. У всех риэлтеров есть пропуски. Их где-то ~50%. Похоже, что это какая-то внутреняя особеность объявлений."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Восстановление пропусков\n",
    "Для начала преобразуем формат улицы: вначале идет номер дома, потом название улицы. Уберем номер дома.\n",
    "Для каждой улицы посчитаем среднюю кооридианту среди непропусов:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df_p['Address'] = df_p['Address'].apply(lambda x: ' '.join(x.split()[1:]))\n",
    "df_p.head()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Пропуски координат\n",
    "Сделаем их как среднее по улице\\району\\почтовому коду"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df_full_cor = df_raw[['Suburb', 'Address', 'Postcode', 'Lattitude', 'Longtitude']].dropna()\n",
    "df_full_cor['Address'] = df_full_cor['Address'].apply(lambda x: ' '.join(x.split()[1:]))\n",
    "df_full_cor.head()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def cor_replacer(x, col):\n",
    "    if not pd.isnull(x[col]):\n",
    "        return x[col]\n",
    "    \n",
    "    if x['Address'] in df_full_cor['Address'].values:\n",
    "        return df_full_cor[df_full_cor['Address'] == x['Address']][col].mean()\n",
    "    \n",
    "    if x['Suburb'] in df_full_cor['Suburb'].values:\n",
    "        return df_full_cor[df_full_cor['Suburb'] == x['Suburb']][col].mean()\n",
    "    \n",
    "    if x['Postcode'] in df_full_cor['Postcode'].values:\n",
    "        return df_full_cor[df_full_cor['Postcode'] == x['Postcode']][col].mean() \n",
    "    \n",
    "    print(x.Suburb, col)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df_p['Lattitude'] = df_p.apply(cor_replacer, axis=1, col='Lattitude')\n",
    "df_p['Longtitude'] = df_p.apply(cor_replacer, axis=1, col='Longtitude')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Пропуски Bathroom, Car, Landsize, BuildingArea, YearBuilt \n",
    "Аналогично прошлому, но с учетом типа жилья"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df_full_cor = df_raw[['Suburb', 'Address', 'Postcode', 'Type', 'Bathroom', 'Car', 'Landsize', 'BuildingArea', 'YearBuilt']].dropna()\n",
    "df_full_cor['Address'] = df_full_cor['Address'].apply(lambda x: ' '.join(x.split()[1:]))\n",
    "df_full_cor.head()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def value_replacer(x, col):\n",
    "    if not pd.isnull(x[col]):\n",
    "        return x[col]\n",
    "    \n",
    "    if x['Address'] in df_full_cor['Address'].values:\n",
    "        return df_full_cor[df_full_cor['Address'] == x['Address']][col].mean()\n",
    "    \n",
    "    if x['Suburb'] in df_full_cor['Suburb'].values:\n",
    "        return df_full_cor[df_full_cor['Suburb'] == x['Suburb']][col].mean()\n",
    "    \n",
    "    if x['Postcode'] in df_full_cor['Postcode'].values:\n",
    "        return df_full_cor[df_full_cor['Postcode'] == x['Postcode']][col].mean() \n",
    "    \n",
    "    print(x.Suburb, col)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def cor_replacer(x, col):\n",
    "    if not pd.isnull(x[col]):\n",
    "        return x[col]\n",
    "    \n",
    "    if x['Address'] in df_full_cor['Address'].values:\n",
    "        return df_full_cor[df_full_cor['Address'] == x['Address']][col].mean()\n",
    "    \n",
    "    if x['Suburb'] in df_full_cor['Suburb'].values:\n",
    "        return df_full_cor[df_full_cor['Suburb'] == x['Suburb']][col].mean()\n",
    "    \n",
    "    if x['Postcode'] in df_full_cor['Postcode'].values:\n",
    "        return df_full_cor[df_full_cor['Postcode'] == x['Postcode']][col].mean() \n",
    "    \n",
    "    print(x.Suburb, col, x.Type)\n",
    "    return df_full_cor[col].mean()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df_p['Bathroom'] = df_p.apply(cor_replacer, axis=1, col='Bathroom')\n",
    "df_p['Car'] = df_p.apply(cor_replacer, axis=1, col='Car')\n",
    "df_p['Landsize'] = df_p.apply(cor_replacer, axis=1, col='Landsize')\n",
    "df_p['BuildingArea'] = df_p.apply(cor_replacer, axis=1, col='BuildingArea')\n",
    "df_p['YearBuilt'] = df_p.apply(cor_replacer, axis=1, col='YearBuilt')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "drop = np.invert(df_p.isnull().any(axis=1))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df_p[df_p.isnull().any(axis=1)]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Построение модели\n",
    "Для тестирования будет использовать 20% данных.\n",
    "Обучение на 80%.\n",
    "\n",
    "Т.к. это задача регресии, то в качестве метрики здесь будет использоваться Mean Squared Error. Это связано с тем, что модель не должна допускать больших отклонений от истиной стоимости. В самом деле, предположим, что у нас есть 5 домов, которые стоят по 1000. Пусть первая модель даст предсказания 1000, 500, 1000, 1000, 1000, вторая 900 900 900 900 900. У обеих моделей абсолюная ошибка будет одинаковой (значит, с точки зрения метрик абсолютных ошибок - модели одинаковы), но на деле первая модель хуже. Это связано с тем, что человек, который продал дом в 2 раза дешевле, будет крайне недоволен.\n",
    "\n",
    "В задаче есть сметь категориальных и количественных признаков. Поэтому для решения будем использовать деревья. Так же деревья не так сильно бояться выбросов и плохого масштаба признаков. \n",
    "\n",
    "Данные имеют распределение по времени, так что будет использовать более новые для теста."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from sklearn.ensemble import RandomForestRegressor\n",
    "from sklearn.model_selection import cross_val_score, train_test_split, GridSearchCV, KFold\n",
    "import xgboost\n",
    "from sklearn.preprocessing import LabelEncoder \n",
    "from collections import defaultdict\n",
    "from sklearn.metrics import mean_squared_error"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Работа с фичами\n",
    "1) переботаем дату. Учитывать конкретную неделю\\день не имеет смысла: продажа\\покупки долгий и заранее планируемый процесс."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df_p['year'] = df_p['Date'].apply(lambda x: x.year)\n",
    "df_p['month'] = df_p['Date'].apply(lambda x: x.month)\n",
    "df_p.drop('Date', axis=1, inplace=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df_p.head()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Уберем 3 фичи с пропусками непонятный природы"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "y = np.log(df.Price.values[drop])\n",
    "X = df_p[drop]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "split_id = int(0.8*X.shape[0])\n",
    "split_id"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Закодируем категориальные данные данные. Кодировщики сохранятся в словаре для дальнейшего использования."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "decoders = defaultdict(LabelEncoder)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "categorial_data = ['Suburb', 'Address', 'Type', 'Method', 'SellerG', 'Postcode', 'CouncilArea', 'Regionname']"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "for col in categorial_data:\n",
    "    print(col)\n",
    "    X[col] = decoders[col].fit_transform(X[col])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "X[X.isnull().any(axis=1)]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Разделим на тест и треин"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "X_train, X_test = X[:split_id], X[split_id:]\n",
    "y_train, y_test = y[:split_id], y[split_id:]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "X_train.shape, X_test.shape"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Поиск лучших гиперпараметров для случайного леса. В случае случайного леса большее количество деревьев не должно ухудать количество предсказаний (проворено, это так). Поэтому будем настраиваеть только глубину, в целях экономии времени. В дальнейшем будет показано, что файнтьюн не особо помогает и нет смысла тратить на машинное него время.\n",
    "\n",
    "\n",
    "Для кросвалидации будем использовать разделение на 4 фолда с перемешиванием. Это компенсирует эффект заглядывания \"в будущее\". Cпойлер: дата окажется не очень важной фичей."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%time\n",
    "params = {'n_estimators':np.arange(10, 120, 10),\n",
    "          'max_depth':np.arange(1, 12, 2)\n",
    "         }\n",
    "\n",
    "params = {'max_depth':np.arange(1, 25, 2)\n",
    "         }\n",
    "\n",
    "best_model = GridSearchCV(RandomForestRegressor(n_estimators=100, random_state=1984), \n",
    "                          params, \n",
    "                          scoring='neg_mean_squared_error', \n",
    "                          cv=KFold(n_splits=4, random_state=1984, shuffle=True),\n",
    "                          n_jobs=-1)\n",
    "best_model.fit(X_test, y_test)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "best_model.best_params_"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%time\n",
    "\n",
    "rf_reg = RandomForestRegressor(n_estimators=2000, max_depth=17, n_jobs=-1, random_state=1984)\n",
    "rf_reg.fit(X_train, y_train)\n",
    "\n",
    "pred = rf_reg.predict(X_test)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "mean_squared_error(y_test, pred)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "В среднем ошибаемся на 15% цены:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "np.mean(np.abs(np.exp(pred) - np.exp(y_test))) / np.exp(y_test).mean()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Важность фич"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "f_impt=pd.DataFrame({'value':rf_reg.feature_importances_,\n",
    "                     'columns':X_test.columns})\n",
    "f_impt.head()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "a4_dims = (11.7, 8.27)\n",
    "fig, ax = plt.subplots(figsize=a4_dims)\n",
    "\n",
    "g = sns.barplot(x='columns', y='value', data=f_impt, ax=ax);\n",
    "g.set_xticklabels(g.get_xticklabels(), rotation=90);"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Вполне ожидаемо, что наиболее важными фичами оказались Количество комнат, тип жилья и расстояние до центра.\n",
    "\n",
    "Улица и район имеют малое значение. Возможно, что это компенсированно фичами широты и долготы, а так же индекса, которые тоже отвечают за географическое положение. Вообще, это не одно и тоже, но достаточно близко.\n",
    "\n",
    "Количество парковочных мест оказалось не слишком важной фичей, что странно."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Буст\n",
    "Попробуем более сложную модель дерева. Ради эксперимента, попробуем её переобучить на тестовую выборку.\n",
    "Поиск оптимальных параметров будет делать с помощью гиперопта."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from xgboost import XGBClassifier\n",
    "from hyperopt import STATUS_OK, Trials, fmin, hp, tpe\n",
    "import xgboost as xgb"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "dtrain = xgb.DMatrix(X_train, label=y_train)\n",
    "dvalid = xgb.DMatrix(X_test, label=y_test)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def score(params):\n",
    "    print(\"Training with params: \")\n",
    "    print(params)\n",
    "    num_round = int(params['n_estimators'])\n",
    "    del params['n_estimators']\n",
    "    \n",
    "    watchlist = [(dvalid, 'eval'), (dtrain, 'train')]\n",
    "    gbm_model = xgb.train(params, dtrain, num_round,\n",
    "                          evals=watchlist,\n",
    "                          verbose_eval=True)\n",
    "    predictions = gbm_model.predict(dvalid,\n",
    "                                    ntree_limit=gbm_model.best_iteration + 1)\n",
    "    #return predictions\n",
    "    score = mean_squared_error(y_test, predictions)\n",
    "    # TODO: Add the importance for the selected features\n",
    "    with open('../export/log_boost.txt', 'a') as f:\n",
    "        print(\"\\tScore {0}\".format(score), num_round, params, file=f, sep='\\t')\n",
    "    print(\"\\tScore {0}\\n\\n\".format(score))\n",
    "    loss = score\n",
    "    return {'loss': loss, 'status': STATUS_OK}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "space = {\n",
    "    'n_estimators': hp.quniform('n_estimators', 30, 350, 1),\n",
    "    'eta': hp.quniform('eta', 0.025, 0.5, 0.025),\n",
    "    'max_depth':  hp.choice('max_depth', np.arange(1, 14, dtype=int)),\n",
    "    'min_child_weight': hp.quniform('min_child_weight', 1, 6, 1),\n",
    "    'subsample': hp.quniform('subsample', 0.5, 1, 0.05),\n",
    "    'gamma': hp.quniform('gamma', 0.5, 1, 0.05),\n",
    "    'colsample_bytree': hp.quniform('colsample_bytree', 0.5, 1, 0.05),\n",
    "    'eval_metric': 'rmse',\n",
    "    'objective': 'reg:linear',\n",
    "    'nthread': 7,\n",
    "    'booster': 'gbtree',\n",
    "    'silent': 1,\n",
    "    'seed': 1984\n",
    "}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "best = fmin(score, space, algo=tpe.suggest, max_evals=100)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "space_best = space\n",
    "for k, v in best.items():\n",
    "    space_best[k] = v\n",
    "    \n",
    "num_round = int(space_best['n_estimators'])\n",
    "del space_best['n_estimators']\n",
    "\n",
    "space_best"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "gbm_model = xgb.train(space_best, dtrain, num_round,\n",
    "                      verbose_eval=False)\n",
    "gbm_predictions = gbm_model.predict(dvalid, ntree_limit=gbm_model.best_iteration + 1)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "np.mean((np.abs(np.exp(gbm_predictions) - np.exp(y_test)))/np.exp(y_test))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Примерно, тот же результат, что и у леса"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Вывод\n",
    "Как можно заметить, использование переобученного буста не дало значимого прироста результата.\n",
    "Таким образом, можно использовать обычный случайный лес (увелчение количества деревьев не дает значимого прироста результатов). \n",
    "\n",
    "Думаю, что часть ошибки связана с параметрами, которые не входят в исходный датасет: срочность продажи, состояния здания, ремонт, субъективность оценки, ожидание каких-то новых событий (например, метро через пару лет).\n",
    "\n",
    "Модель можно применять на сайтах продажи недвижимости: после заполнения параметров недвижимости, продавцу будет показываться ориентировачная цена. \n",
    "\n",
    "Другой вариант: сервис по защите от мошенников. Приложение\\сайт будет показывать ориентировочную цену жилья в этом райное, что защитит покупателей\\продавцов от нечестный предложений."
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.4"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
